package com.hqyj.javaSpringBoot.modules.common.controller;

import com.hqyj.javaSpringBoot.modules.common.entity.ExceptionLog;
import com.hqyj.javaSpringBoot.modules.common.service.ExceptionLogService;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;

/**
 * 控制层异常处理器
 *
 * 知识点1：
 *      @ControllerAdvice 捕获全局的控制层所有异常
 *      @ExceptionHandler 局部/定点 精确捕获异常
 *
 * 知识点2：如何获取请求中对应的方法和类的信息？
 *      通过 request里的 org.springframework.web.servlet.HandlerMapping.bestMatchingHandler 属性得到 HandlerMethod 方法，
 *      然后通过 HandlerMethod 方法得到 方法名和类名 以及 方法和类 上面的注解
 *
 * 知识点3：理清 BasicErrorController 业务逻辑，
 *          模仿该架构，换成我们的业务逻辑，用来处理非控制层的 Error 异常（如 404）
 *
 */
@ControllerAdvice
public class ExceptionHandleController {

    private final static Logger LOGGER =
            LoggerFactory.getLogger(ExceptionHandleController.class);

    @Autowired
    private ExceptionLogService exceptionLogService;

    // 使用ModelAndView 可返回错误页面和 json 数据
//    @ExceptionHandler(value = {ArithmeticException.class})
    @ExceptionHandler(value = {Exception.class})
    public ModelAndView exceptionHandle(HttpServletRequest request, Exception e){
        LOGGER.debug("===== Catch Exception =====");
        LOGGER.debug(e.getMessage());
        e.printStackTrace();

        // 1.在控制层捕获异常，根据异常的类型确定状态值 ---- 403 || 500
        HttpStatus httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
        String errorPath = "/common/500";
        String message = "Internal Server Error.";
        // 先假定 ArithmeticException 是没有权限异常
        if (e instanceof ArithmeticException) {
            httpStatus = HttpStatus.FORBIDDEN;
            errorPath = "/common/403";
            message = "No Permission.";
        }

        // 2.保存异常信息
        saveExceptionLog(request, e);

        // 3.判断访问是 view 还是 数据接口
        // 4.如果是 view 则返回对应的错误页面，如果是 数据接口，则返回 Result 接口数据
        if (isInterface(request)) {
            // 返回 json 数据
            return returnJson(errorPath, httpStatus, message);
        } else {
            // 返回错误页面
            return returnView(errorPath, httpStatus);
        }
    }

    // 返回 json
    private ModelAndView returnJson(String path, HttpStatus httpStatus, String message){
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("status", httpStatus.value());
        modelAndView.addObject("message", message);
        modelAndView.addObject("object", path);
        return modelAndView;
    }

    // 返回view
    private ModelAndView returnView(String path, HttpStatus httpStatus){
        return new ModelAndView("redirect:" + path, httpStatus);
    }

    // 判断是否为接口，从 request域判断
    private boolean isInterface(HttpServletRequest request){
        HandlerMethod handlerMethod = (HandlerMethod) request.getAttribute(
                "org.springframework.web.servlet.HandlerMapping.bestMatchingHandler");
        if (handlerMethod == null) {
            return false;
        }
        // 不为null，找指定注解
        ResponseBody[] methodAnnotations =
                handlerMethod.getMethod().getAnnotationsByType(ResponseBody.class);
        ResponseBody[] typeAnnotations1 =
                handlerMethod.getBeanType().getAnnotationsByType(ResponseBody.class);
        RestController[] typeAnnotations2 =
                handlerMethod.getBeanType().getAnnotationsByType(RestController.class);
        return methodAnnotations.length > 0 ||
                typeAnnotations1.length > 0 ||
                typeAnnotations2.length > 0;
    }

    // 保存异常信息
    public void saveExceptionLog(HttpServletRequest request, Exception e){
        ExceptionLog exceptionLog = new ExceptionLog();
        // 获取 ip 和 路径
        exceptionLog.setIp(request.getRemoteAddr());
        exceptionLog.setPath(request.getRequestURI());

        // 从 HandlerMethod 在request域中获取 类名 和 方法名
        //     org.springframework.web.servlet.HandlerMapping.bestMatchingHandler (通过打断点分析得到)
        HandlerMethod handlerMethod = (HandlerMethod) request.getAttribute(
                "org.springframework.web.servlet.HandlerMapping.bestMatchingHandler");
        if (handlerMethod != null) {
            exceptionLog.setClassName(handlerMethod.getBeanType().getName());
            exceptionLog.setMethodName(handlerMethod.getMethod().getName());
        }

        // ExceptionType异常类型 从异常的类名上取
        exceptionLog.setExceptionType(e.getClass().getSimpleName());

        // 取ExceptionMessage异常信息，为防止异常信息过多撑爆数据库；
        //      如果截取异常信息长度>200个字符就取200个，否则只取当前长度
        //      如果为空则返回空字符即可
        exceptionLog.setExceptionMessage(StringUtils.isBlank(e.getMessage()) ? "" :
                e.getMessage().substring(0,
                        e.getMessage().length() > 200 ? 200 : e.getMessage().length()));

        exceptionLogService.insertExceptionLog(exceptionLog);
    }
}
